/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.fourtwelve.thread_execution;

import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author INSPIRON_ADMIN
 */
public class ThreadPool extends Thread {

    private static final Logger log = Logger.getLogger(ThreadPool.class.getName());
    private static volatile ThreadPool instance = null;
    private final int NUM_THREADS = 2;
    private final Object RUNNABLE_QUEUE_LOCK;
    private final Queue<Runnable> runnableQueue;
    private final Object CALLABLE_QUEUE_LOCK;
    private final Queue<Callable<?>> callableQueue;
    private final Object FUTURE_QUEUE_LOCK;
    private final Queue<Future<?>> futureQueue;
    private final ExecutorService executor;
    private final Object WAIT_NOTIFY;
    
    public static ThreadPool getInstance() {
        synchronized (ThreadPool.class) {
            if (instance == null) {
                instance = new ThreadPool();
            }
        }
        return instance;
    }

    private ThreadPool() {
        RUNNABLE_QUEUE_LOCK = new Object();
        runnableQueue = new LinkedList<Runnable>();

        CALLABLE_QUEUE_LOCK = new Object();
        callableQueue = new LinkedList<Callable<?>>();

        FUTURE_QUEUE_LOCK = new Object();
        futureQueue = new LinkedList<Future<?>>();
        
        WAIT_NOTIFY = new Object();

        executor = Executors.newFixedThreadPool(NUM_THREADS);
    }

    @Override
    public void run() {
        while (true) {
            boolean allQueuesEmpty = this.isTaskQueuesEmpty();

            while (!allQueuesEmpty) {
                synchronized (RUNNABLE_QUEUE_LOCK) {
                    if (!runnableQueue.isEmpty()) {
                        try {
                            executor.execute(runnableQueue.remove());
                        } catch (NoSuchElementException e) {
                            log.log(Level.WARNING, "Queue was empty when remove was called", e);
                        }
                    }
                }

                synchronized (CALLABLE_QUEUE_LOCK) {
                    if (!callableQueue.isEmpty()) {
                        try {
                            Future<?> tmp = executor.submit(callableQueue.remove());
                            enqueueFuture(tmp);
                        } catch (NoSuchElementException e) {
                            log.log(Level.WARNING, "Queue was empty when remove was called", e);
                        }
                    }
                }
                allQueuesEmpty = this.isTaskQueuesEmpty();
                log.log(Level.CONFIG, "allQueue's empty = {0}", new Object[]{allQueuesEmpty});
            }

            allQueuesEmpty = this.isTaskQueuesEmpty();
            if (allQueuesEmpty) {
                try {
                    synchronized (WAIT_NOTIFY) {
                        WAIT_NOTIFY.wait();
                    }
                } catch (InterruptedException ex) {
                    log.log(Level.SEVERE, "Thread sleep interrupted", ex);
                } catch (IllegalArgumentException ex) {
                    log.log(Level.SEVERE, "Thread sleep time value invalid", ex);
                }
            }
        }
    }

    /**
     * add a Runnable Object to the task queue
     *
     * @param task
     * @return true if
     * @param task was successfully enqueued false otherwise
     */
    public boolean enqueueRunnable(Runnable task) {
        boolean success;
        synchronized (RUNNABLE_QUEUE_LOCK) {
            log.log(Level.CONFIG, "Enqueueing task");
            success = runnableQueue.add(task);
            log.log(Level.CONFIG, "task enqueued = {0}", success);
        }
        if(success) {
            synchronized (WAIT_NOTIFY) {
                WAIT_NOTIFY.notify();
            }
        }
        return success;
    }

    /**
     * add a Runnable Object to the task queue
     *
     * @param task
     * @return true if
     * @param task was successfully enqueued false otherwise
     */
    public boolean enqueueRunnable(Callable<?> task) {
        boolean success;
        synchronized (CALLABLE_QUEUE_LOCK) {
            success = callableQueue.add(task);
        }
        if(success) {
            synchronized (WAIT_NOTIFY) {
                WAIT_NOTIFY.notify();
            }
        }
        return success;
    }

    /**
     * add a Future Object to the futures queue
     *
     * @param future
     * @return true if
     * @param future was successfully enqueued false otherwise
     */
    public boolean enqueueFuture(Future<?> future) {
        boolean success;
        synchronized (FUTURE_QUEUE_LOCK) {
            success = futureQueue.add(future);
        }
        return success;
    }

    public Queue<Runnable> shutdownThreadPool(boolean immediately) {
        Queue<Runnable> unfinished = null;

        if (!immediately) {
            executor.shutdown();
        } else {
            unfinished = (Queue<Runnable>) executor.shutdownNow();
        }
        return unfinished;
    }

    private boolean isTaskQueuesEmpty() {
        boolean allQueuesEmpty;
        synchronized (RUNNABLE_QUEUE_LOCK) {
            allQueuesEmpty = runnableQueue.isEmpty();
        }

        synchronized (CALLABLE_QUEUE_LOCK) {
            allQueuesEmpty = allQueuesEmpty && callableQueue.isEmpty();
        }
        return allQueuesEmpty;
    }

    public int tasksEnqueued() {
        int tasks;
        synchronized (RUNNABLE_QUEUE_LOCK) {
            tasks = runnableQueue.size();
        }

        synchronized (CALLABLE_QUEUE_LOCK) {
            tasks += callableQueue.size();
        }
        return tasks;
    }

    //    public int tasksRunning() {
//        int tasks;
//        
//        return tasks;
//    }
}
